/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package merlion.mrp.atpm.session;

import java.lang.String;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import merlion.common.entity.JobToDo;
import merlion.common.session.stateless.JobListSessionBeanLocal;
import merlion.crm.sopm.entity.ItemLine;
import merlion.common.util.Consts;
import merlion.crm.slcm.session.SLCSessionBeanLocal;
import merlion.crm.sopm.entity.SalesOrder;
import merlion.mrp.atpm.entity.PendingATP;
import merlion.mrp.ppm.entity.BOM;
import merlion.mrp.ppm.entity.BackOrder;
import merlion.mrp.ppm.entity.BackOrderIndiv;
import merlion.mrp.ppm.entity.DailyMPS;
import merlion.mrp.ppm.entity.DailyPlannedProduction;
import merlion.mrp.ppm.entity.IndexNDate;
import merlion.mrp.ppm.entity.IndexNValue;
import merlion.mrp.ppm.entity.MRP;
import merlion.mrp.ppm.entity.Product;
import merlion.mrp.ppm.entity.Reservation;
import merlion.mrp.ppm.pojo.LongHolder;
import merlion.scm.lcm.entity.DeliveryOrder;
import merlion.mrp.ppm.session.MPSPlanningSessionLocal;
import merlion.mrp.ppm.session.MRPSessionLocal;
import merlion.mrp.ppm.session.SalesOptPlanningSessionLocal;
import merlion.scm.icm.session.InventoryControlSessionBeanLocal;

/**
 *
 * @author Zhang Ying, Zhu Xin
 */
@Stateless
public class ATPSessionBean implements ATPSessionBeanLocal {

    @PersistenceContext
    private EntityManager entityManager;
    @EJB
    private InventoryControlSessionBeanLocal icSession;
    @EJB
    private MPSPlanningSessionLocal mpsSession;
    @EJB
    private SalesOptPlanningSessionLocal sopSession;
    @EJB
    private MRPSessionLocal mrpSession;
    @EJB
    private SLCSessionBeanLocal slcSession;
    @EJB
    private JobListSessionBeanLocal jobListManager;
    private static final SimpleDateFormat formatter = new SimpleDateFormat("MMMM dd, yyyy");

    @Override
    public Timestamp getIndicativeLeadTimeForRFQ(DeliveryOrder order) {

        int numberOfProducts = order.getItemLines().size();
        ArrayList<Timestamp> leadTimes = new ArrayList<Timestamp>(numberOfProducts); //Lead time of all products
        for (ItemLine il : order.getItemLines()) {  //edited by ZX on 11/8 4pm
            if (il.getQtyOrderedCase() == 0) {
                leadTimes.add(new Timestamp(System.currentTimeMillis()));
            } else {
                leadTimes.add(getLeadTimeForIndivProduct(il));
            }
        }
        // get the latest date
        Timestamp max = leadTimes.get(0);
        for (Timestamp ts : leadTimes) {
            if (ts == null) {
                return null;
            } else if (ts.after(max)) {
                max = ts;
            }
        }

//        //edited on 11/8 by ZX , return next day
//        Calendar maxC = Calendar.getInstance();
//        maxC.setTimeInMillis(max.getTime());
//        maxC = getNextWorkingDay(maxC);

        //return new Timestamp(maxC.getTimeInMillis());
        return max;
    }
//
//    private Calendar getNextWorkingDay(Calendar cal) {
//        cal.add(Calendar.DAY_OF_MONTH, 1);
//        while (sopSession.isPublicHoliday(cal) || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
//            cal.add(Calendar.DAY_OF_MONTH, 1);
//        }
//        return cal;
//    }

    @Override
    public ArrayList<ArrayList<Long>> getDailyDemands(ArrayList<Timestamp> changedDates) {
        ArrayList<ArrayList<Long>> result = new ArrayList<ArrayList<Long>>();
        for (int i = 0; i < 4; i++) {
            result.add(new ArrayList<Long>());
        }

        SimpleDateFormat sdf = new SimpleDateFormat("ddMMyyyy");
        Query query = entityManager.createQuery("SELECT d FROM DailyPlannedProduction d");
        List<DailyPlannedProduction> dpps = (List<DailyPlannedProduction>) query.getResultList();

        for (Timestamp productionDay : changedDates) {

            boolean haveThisDay = false;

            for (DailyPlannedProduction d : dpps) {

                String presentedPDate = sdf.format(productionDay);
                String dDate = sdf.format(d.getProductionDay());

                if (presentedPDate.equals(dDate)) {
                    haveThisDay = true;
                    if (d.getProduct().getProductType().equals(Consts.ProductType.BAR_A.getText())) {
                        result.get(0).add(d.getDailyDemand());
                    } else if (d.getProduct().getProductType().equals(Consts.ProductType.BAR_B.getText())) {
                        result.get(1).add(d.getDailyDemand());
                    } else if (d.getProduct().getProductType().equals(Consts.ProductType.BAR_C.getText())) {
                        result.get(2).add(d.getDailyDemand());
                    } else {
                        result.get(3).add(d.getDailyDemand());
                    }
                }
            }
            if (!haveThisDay) {
                result.get(0).add(0L);
                result.get(1).add(0L);
                result.get(2).add(0L);
                result.get(3).add(0L);
            }
        }
        return result;

    }

    @Override
    public ArrayList<ArrayList<Long>> getDailyOTDemands(ArrayList<Timestamp> changedDates) {
        ArrayList<ArrayList<Long>> result = new ArrayList<ArrayList<Long>>();
        for (int i = 0; i < 4; i++) {
            result.add(new ArrayList<Long>());
        }

        SimpleDateFormat sdf = new SimpleDateFormat("ddMMyyyy");
        Query query = entityManager.createQuery("SELECT d FROM DailyPlannedProduction d");
        List<DailyPlannedProduction> dpps = (List<DailyPlannedProduction>) query.getResultList();

        for (Timestamp productionDay : changedDates) {

            boolean haveThisDay = false;

            for (DailyPlannedProduction d : dpps) {

                String presentedPDate = sdf.format(productionDay);
                String dDate = sdf.format(d.getProductionDay());

                if (presentedPDate.equals(dDate)) {
                    haveThisDay = true;
                    if (d.getProduct().getProductType().equals(Consts.ProductType.BAR_A.getText())) {
                        result.get(0).add(d.getDailyOTDemand());
                    } else if (d.getProduct().getProductType().equals(Consts.ProductType.BAR_B.getText())) {
                        result.get(1).add(d.getDailyOTDemand());
                    } else if (d.getProduct().getProductType().equals(Consts.ProductType.BAR_C.getText())) {
                        result.get(2).add(d.getDailyOTDemand());
                    } else {
                        result.get(3).add(d.getDailyOTDemand());
                    }
                }
            }
            if (!haveThisDay) {
                result.get(0).add(0L);
                result.get(1).add(0L);
                result.get(2).add(0L);
                result.get(3).add(0L);
            }
        }
        return result;
    }

    private Timestamp getLeadTimeForIndivProduct(ItemLine il) {
        Timestamp tp = new Timestamp(System.currentTimeMillis());
        Long shortfall = checkCurrentInventory(il.getProduct(), il.getQtyOrderedCase());
        if (shortfall <= 0) {
            return tp;
        }
        System.out.println("@@@@@@@@@@@@@@@@@@ATPSession at line 199, shortfall for il " + il.getId() + " : " + shortfall);
        tp = checkPlannedProduction(il.getProduct(), shortfall);
        return tp;
    }

    @Override
    public String getIndicativeLeadTimeforUrgent(DeliveryOrder order, Timestamp requestedDate) {
        String s = "";
        Timestamp ts = getIndicativeLeadTimeForRFQ(order);
        requestedDate.setTime(requestedDate.getTime() + 8 * 60 * 60 * 1000);
        System.out.println("Requested date: " + requestedDate.toString() + " " + requestedDate.getTime() / 86400000);

        if (ts != null) {
            System.out.println("ts date: " + ts.toString() + " " + ts.getTime() / 86400000);
        } else {
            System.out.println("ts date: isnull");
        }

        if (ts == null) {
            return Consts.ATP_SO_FAIL;
        } else if (ts != null && requestedDate != null && ts.getTime() / 86400000 +1L<= requestedDate.getTime() / 86400000) {
            s = formatter.format(requestedDate);//Current Plan able to fulfill
            return s;
        } else if (ts != null && ts.getTime() / 86400000 > requestedDate.getTime() / 86400000 +1l) {
            s = "Pending"; //Current Plan not able to fulfill. Please wait for manager's decision
        } else {
            return Consts.ATP_SO_FAIL;
        }
        //create pending job
//        PendingATP pending = new PendingATP(Consts.PENDING_RFQ, requestedDate);  
//        DeliveryOrder deo = getDeliveryOrderById(order.getId());
//        pending.setDeliveryOrder(deo);
//        entityManager.merge(pending);
        return s;
    }

    @Override
    public String getConfirmedLeadTime(DeliveryOrder order, Timestamp indicativeLT) {
        System.out.println("ATP 1");

        //entityManager.flush();

        String s = "";
        Timestamp ts = getIndicativeLeadTimeForRFQ(order);
        SimpleDateFormat sdf = new SimpleDateFormat("MMMM dd, yyyy");
        // System.out.println("ATP SESSION BEAN TESTING::::::::: IndicativeLT: " + sdf.format(indicativeLT));
        //  System.out.println("ATP SESSION BEAN TESTING::::::::: ts: " + sdf.format(ts));
        if (ts != null && indicativeLT != null && ts.getTime() / 86400000 +1l<= indicativeLT.getTime() / 86400000) {
            reserveForSalesOrder(order, indicativeLT);

            order = entityManager.find(DeliveryOrder.class, order.getId());
            s = "Available"; //Current Plan able to fulfill

            order.setLeadTime(sdf.format(indicativeLT));
            order.setLeadTimeTS(indicativeLT);
            order.getSalesOrder().setSalesOrderStatus(Consts.ORDER_WAITING_CONFIRMATION);
            entityManager.merge(order);

            entityManager.merge(order.getSalesOrder());

            entityManager.flush();

            // TODO: create job for available
            JobToDo jobTD = new JobToDo();
            jobTD.setJobType(Consts.JOB_TYPE_ATP_SO);
            jobTD.setRelatedId(order.getSalesOrder().getId());
            jobTD.setStatus(Consts.JOB_STATUS_PENDING);
            Calendar calendar = Calendar.getInstance();
            Date date = calendar.getTime();
            Timestamp currentTime = new Timestamp(date.getTime());
            jobTD.setCreatedDate(currentTime);
            SalesOrder so = jobListManager.getRelatedSalesOrder(order.getSalesOrder().getId());
            jobTD.setTitle(Consts.JOB_TYPE_ATP_SO_TITLE);
            jobTD.setContent(Consts.JOB_TYPE_ATP_SO_CONTENT + " " + so.getSalesOrderIdStr());
            jobListManager.addNewJob(so.getCustomer().getCustomerExecutive().getSystemUserAccount().getId(), jobTD);


            return s;
        } else if (ts != null && indicativeLT != null && ts.getTime() / 86400000 > indicativeLT.getTime() / 86400000 +1l) {
            order = entityManager.find(DeliveryOrder.class, order.getId());
            s = "Pending"; //current plan is not able to fulfill order. Please wait for manager's decision
            order.setLeadTime(s);
            order.getSalesOrder().setSalesOrderStatus(Consts.ORDER_ATPPENDING);
            entityManager.merge(order);

            entityManager.merge(order.getSalesOrder());


            entityManager.flush();
//
//            // create job for available (no need, agreed on 11.7 11:48am)
//            JobToDo jobTD = new JobToDo();
//            jobTD.setJobType(Consts.JOB_TYPE_ATP_SO_PENDING);
//            jobTD.setRelatedId(order.getSalesOrder().getId());
//            jobTD.setStatus(Consts.JOB_STATUS_PENDING);
//            Calendar calendar = Calendar.getInstance();
//            Date date = calendar.getTime();
//            Timestamp currentTime = new Timestamp(date.getTime());
//            jobTD.setCreatedDate(currentTime);
//            SalesOrder so = jobListManager.getRelatedSalesOrder(order.getSalesOrder().getId());
//            jobTD.setTitle(Consts.JOB_TYPE_ATP_SO_PENDING_TITLE);
//            jobTD.setContent(Consts.JOB_TYPE_ATP_SO_PENDING_CONTENT + " " + so.getSalesOrderIdStr());
//            jobListManager.addNewJob(so.getCustomerExecutive().getSystemUserAccount().getId(), jobTD);

        } else {
            s = Consts.ATP_SO_FAIL;
            order.setLeadTime(s);
            order.getSalesOrder().setSalesOrderStatus(Consts.ORDER_ATPFAILED);
            entityManager.merge(order);
            entityManager.merge(order.getSalesOrder());
            entityManager.flush();

            //no job list, agreed on 11.7 11:49am
        }

        //create pending job
        PendingATP pending = new PendingATP(Consts.PENDING_SO, indicativeLT);
        DeliveryOrder deo = getDeliveryOrderById(order.getId());
        pending.setDeliveryOrder(deo);

        entityManager.merge(pending);
        entityManager.flush();
        return s;
    }

    //TO DO: remember to delete pending after done.
    public boolean reserveForSalesOrder(DeliveryOrder order, Timestamp indicativeLT) {
        boolean success = true;
        for (ItemLine il : order.getItemLines()) {
            if (il.getQtyOrderedCase() != 0) {
                Long shortfall = checkCurrentInventory(il.getProduct(), il.getQtyOrderedCase());
                if (shortfall <= 0) { //current inventory is enough
                    System.out.println("Reserve Inventory");
                    if (!icSession.reserveInventory(il, il.getQtyOrderedCase())) {
                        success = false;
                    }
                } else {
                    //reserve inventory
                    Long reserveQty = icSession.getRealInventory(il.getProduct().getProductType());
                    if (!icSession.reserveInventory(il, reserveQty)) {
                        success = false;
                    }
                    //reserve planned production
                    if (!reservePlannedProduction(il, shortfall, indicativeLT, order)) {
                        success = false;
                    }
                }
            }

        }
        return success;
    }

    public boolean reservePlannedProduction(ItemLine il, Long qty, Timestamp indicativeLT, DeliveryOrder order) {
        Timestamp current = new Timestamp(System.currentTimeMillis());
        ArrayList<DailyPlannedProduction> dpp = getDailyPlannedProductionByDateAndProduct(il.getProduct(), current);

        Reservation reservation = null;
        DeliveryOrder deo = getDeliveryOrderById(order.getId());
        int counter = 0;
        while (qty > 0 && ((indicativeLT.getTime() / 86400000 - dpp.get(counter).getProductionDay().getTime() / 86400000) >= 0)) {
            //reserve the production only until indicative lead time
            Long indivAvailable = dpp.get(counter).getAvailable();
            if (qty >= dpp.get(counter).getAvailable()) {
                reservation = new Reservation(dpp.get(counter).getAvailable());
            } else {
                reservation = new Reservation(qty);
            }
            reservation.setDeliveryOrder(deo);
            dpp.get(counter).getReservations().add(reservation);
            reservation.setDpp(dpp.get(counter));
            entityManager.merge(dpp.get(counter));   //TODO: check can work or not
            qty -= indivAvailable;
            counter++;
        }
        if (qty <= 0) {
            return true;
        } else {
            return false;
        }

    }

    private DeliveryOrder getDeliveryOrderById(Long id) {
        try {
//            Query query = entityManager.createQuery("SELECT order FROM DeliveryOrder order WHERE order.id=:p1");
//            query.setParameter("p1", id);
//            return (DeliveryOrder) query.getSingleResult();
            return entityManager.find(DeliveryOrder.class, id);
        } catch (Exception e) {
            return null;
        }
    }

    public Long checkCurrentInventory(Product p, Long quantity) {
        //This method takes in the requirements for a particular procust and return the amount in shortage
        // get avaliable current inventory
        Long available = icSession.getRealInventory(p.getProductType());
        return quantity - available;

    }

    public Timestamp checkPlannedProduction(Product p, Long quantityOutstanding) {

        // total of (dailyPlannedProd - reservation) >= requested qty, for each product, get the longest lead time
        // if the qty is too big, return null
        // qtyOtsding : requsted qty - current avail inventory
        Calendar cal = Calendar.getInstance();
        ArrayList<DailyPlannedProduction> dpp = getDailyPlannedProductionByDateAndProduct(p, new Timestamp(cal.getTimeInMillis()));
        if (dpp.isEmpty()) {
            return null;
        }
        int counter = 0;
        while (quantityOutstanding > 0) {
            if (counter >= dpp.size()) {
                return null;
            }
            System.out.println("@@@ ATP Session line 416 quantity outstanding at day " + counter + " :" + quantityOutstanding);
            System.out.println("@@@ ATP Session line 417 available at day  " + counter + " :" + dpp.get(counter).getAvailable());
            quantityOutstanding -= dpp.get(counter).getAvailable();
            counter++;
        }
        return dpp.get(counter - 1).getProductionDay();
    }

    public ArrayList<DailyPlannedProduction> getDailyPlannedProductionByDateAndProduct(Product p, Timestamp pd) {
        Date d = new Date(pd.getTime());
        Calendar dCal = Calendar.getInstance();
        dCal.setTime(d);
        dCal.add(Calendar.DAY_OF_MONTH, -1);
        Timestamp converted = new Timestamp(dCal.getTimeInMillis());
        //query got potential bug
        Query query = entityManager.createQuery("SELECT dpp FROM DailyPlannedProduction dpp WHERE dpp.product.productType=:p1 AND dpp.productionDay>:p2");
        query.setParameter("p1", p.getProductType());
        query.setParameter("p2", converted);
        return new ArrayList<DailyPlannedProduction>(query.getResultList());
    }

    @Override
    public Collection<PendingATP> getPendingATPForRFQs() {
        Query query = entityManager.createQuery("SELECT p FROM PendingATP p WHERE p.type=:p1");
        query.setParameter("p1", Consts.PENDING_RFQ);
        return query.getResultList();
    }

    @Override
    public Collection<PendingATP> getPendingATPForSos() {
        Query query = entityManager.createQuery("SELECT p FROM PendingATP p WHERE p.type=:p1");
        query.setParameter("p1", Consts.PENDING_SO);
        return query.getResultList();
    }

    @Override
    public Timestamp simulateSo(ArrayList<ArrayList<LongHolder>> dailyDemands, ArrayList<ArrayList<LongHolder>> otDailyDemands, ArrayList<ArrayList<Long>> differences, ArrayList<Calendar> dates, PendingATP patp) {

        return simulateUrgentRfq(dailyDemands, otDailyDemands, differences, dates, patp);
    }

    //deleted by ZX on 11/9 12:42pm
//    private int getWeekNo(Calendar date) {
//        Calendar monday = mpsSession.getFirstMondayOfMonth(date.get(Calendar.YEAR), date.get(Calendar.MONTH) + 1);
//        int weekNo;
//        if (date.get(Calendar.WEEK_OF_YEAR) >= monday.get(Calendar.WEEK_OF_YEAR)) {
//            weekNo = monday.get(Calendar.WEEK_OF_MONTH) - date.get(Calendar.WEEK_OF_MONTH) + 1;
//        }
//        else {
//            monday = mpsSession
//        }
//        return weekNo;
//    }
    public Long getOnHandValueByDateNMaterial(Calendar date, String materialName) {
        int year, month, weekNo;
        Calendar monday = mpsSession.getFirstMondayOfMonth(date.get(Calendar.YEAR), date.get(Calendar.MONTH) + 1);

        if (date.get(Calendar.WEEK_OF_YEAR) >= monday.get(Calendar.WEEK_OF_YEAR)) {
            year = date.get(Calendar.YEAR);
            month = date.get(Calendar.MONTH) + 1;
            weekNo = date.get(Calendar.WEEK_OF_YEAR) - monday.get(Calendar.WEEK_OF_YEAR) + 1;
        } else {
            if (date.get(Calendar.MONTH) != Calendar.JANUARY) {
                year = date.get(Calendar.YEAR);
                month = date.get(Calendar.MONTH); //month + 1 - 1  previous month
            } else {
                year = date.get(Calendar.YEAR) - 1;
                month = 12;
            }
            monday = mpsSession.getFirstMondayOfMonth(year, month);
            weekNo = date.get(Calendar.WEEK_OF_YEAR) - monday.get(Calendar.WEEK_OF_YEAR) + 1;

        }
        MRP mrp = mrpSession.getMRPByDateAndMaterial(year, month, materialName);
        Long onhand;
        if (mrp != null) {
            onhand = mrp.getPlannedInventory().get(weekNo - 1).getLongValue();
        } else {
            return Long.MAX_VALUE;
        }
        return onhand;
    }

    @Override
    public boolean checkRawMaterial(ArrayList<ArrayList<Long>> differences, ArrayList<Calendar> dates) {

//        Calendar start = dates.get(0);
//        int day = start.get(Calendar.DAY_OF_WEEK); // the day of the first date , the date is confirmed to be a working day
//        Calendar monday = mpsSession.getFirstMondayOfMonth(start.get(Calendar.YEAR), start.get(Calendar.MONTH) + 1);
//        int weekNo = monday.get(Calendar.WEEK_OF_MONTH) - start.get(Calendar.WEEK_OF_MONTH) + 1;
        ArrayList<ArrayList<Long>> weeklyDemand = new ArrayList<ArrayList<Long>>();
        ArrayList<Calendar> datesForWeek = new ArrayList<Calendar>();
        
        for (int i = 0; i < 4; i++) {
            Long amount = 0l;
            datesForWeek = new ArrayList<Calendar>();
            int weekNumber = dates.get(0).get(Calendar.WEEK_OF_YEAR); //weekNo of the first date
            datesForWeek.add(dates.get(0));
            ArrayList<Long> indivWeeklyDemand = new ArrayList<Long>();
            for (int j = 0; j < dates.size(); j++) {
                if (dates.get(j).get(Calendar.WEEK_OF_YEAR) == weekNumber) {//same week
                    amount += differences.get(i).get(j);
                } else {
                    System.out.println("ATPSession at line 519: i" + i + " j: " + " " + dates.get(j).toString());
                    datesForWeek.add(dates.get(j));
                    indivWeeklyDemand.add(amount);
                    amount = differences.get(i).get(j);
                    weekNumber = dates.get(j).get(Calendar.WEEK_OF_YEAR);
                }
            }
            indivWeeklyDemand.add(amount);
            weeklyDemand.add(indivWeeklyDemand);
        }
//   deleted by ZX on 11/9 13:14
//        ArrayList<Long> onHandAmounts = new ArrayList<Long>();
//        for (int i = 0; i < datesForWeek.size(); i++) {
//            Calendar date = datesForWeek.get(i);
//            Long onHand = getOnHandValueByDate(date);
//        }


//        double requirement = 0.0;
//        for (int i = 0; i < weeklyDemand.get(0).size(); i++) {  //for each week
//            //check each week
//            for (String materialName : Consts.allRawMaterialNames) {
//                //get corresponding MRP
//                MRP mrp = mrpSession.getMRPByDateAndMaterial(dates.get(i).get(Calendar.YEAR), dates.get(i).get(Calendar.MONTH) + 1, materialName);
//                Long onHand = mrp.getPlannedInventory().get(weekNumbers.get(i)).getLongValue();
//                BOM bom = mrpSession.getBOM(materialName);
//
//
//                for (int j = 0; j < 4; j++) {  //for each product
//                    double kg;
//                    if (j == 0 || j == 1) {
//                        kg = 12;
//                    } else {
//                        kg = 13.32;
//                    }
//                    System.out.println("testing:::::onhand :" + onHand);
//                    System.out.println("testing:::::requirement acc. :" + requirement);
//                    requirement += weeklyDemand.get(j).get(i) * kg * bom.getMaterialRequirements().get(j).getValue();
//                }
//
//                if (requirement > onHand) {
//                    return false;
//                }
//            }
//        }

        for (Calendar d: datesForWeek) {
            System.out.println("dates for week 565:" + d.toString());
        }
        
        for (int i = 0; i < 4;i ++) {
            for (int j = 0 ; j < datesForWeek.size(); j++) {
                System.out.println("weekly dmeand : i:" + i+" j:" +j+" " + weeklyDemand.get(i).get(j));
            }
        }
 
        for (String materialName : Consts.allRawMaterialNames) {
            double requirement = 0.0;
            for (int i = 0; i < weeklyDemand.get(0).size(); i++) {  //for each week

                //edited by ZX on 11/10 5:06am, if more than 21 days, no need to check
                if (((datesForWeek.get(i).getTimeInMillis() - System.currentTimeMillis()) / 86400000) >= 21) {
                    break;
                }


                //check each week
                //get corresponding MRP

                //deleted by ZX on 11/9 13:15
//                MRP mrp = mrpSession.getMRPByDateAndMaterial(dates.get(i).get(Calendar.YEAR), dates.get(i).get(Calendar.MONTH) + 1, materialName);
//                Long onHand = mrp.getPlannedInventory().get(weekNumbers.get(i)).getLongValue();
                BOM bom = mrpSession.getBOM(materialName);

                Long onHand = getOnHandValueByDateNMaterial(datesForWeek.get(i), materialName);
//                 edited by ZX, another method
//                if(onHand == Long.MAX_VALUE) {
//                    return true;
//                }

                for (int j = 0; j < 4; j++) {  //for each product
                    double kg;
                    if (j == 0 || j == 1) {
                        kg = 12;
                    } else {
                        kg = 13.32;
                    }
                    System.out.println("ATPSession at line 598 : date" + datesForWeek.get(i).toString());
                    System.out.println("ATPSession at line 599 :  weeklyDemand" + weeklyDemand.get(j).get(i));
                    requirement += weeklyDemand.get(j).get(i) * kg * bom.getMaterialRequirements().get(j).getValue();
                    System.out.println("testing:::::onhand :" + onHand);
                    System.out.println("testing:::::requirement acc. :" + requirement);
                }

                if (requirement > onHand) {
                    return false;
                }
            }
        }

        return true;
    }

    @Override
    public Timestamp simulateUrgentRfq(ArrayList<ArrayList<LongHolder>> dailyDemands, ArrayList<ArrayList<LongHolder>> otDailyDemands, ArrayList<ArrayList<Long>> differences, ArrayList<Calendar> dates, PendingATP patp) {
        //check raw material
        //same as above. return null is cannot meet requested date

//        //comment for back order testing,need to add back
//        boolean enough = checkRawMaterial(differences, dates);
//        if (!enough) {
//            return null;
//        }

        ArrayList<Timestamp> leadTimes = new ArrayList<Timestamp>();
        for (int i = 0; i < 4; i++) {
            leadTimes.add(null);
        }

        //deduct current inventory
        //get all Available. get lead time for each product
        for (ItemLine il : patp.getDeliveryOrder().getItemLines()) { //for each product

            //edited by ZX on 11/8 4:05pm  delivery order always have 4 itemlines
            Product product = il.getProduct();
            int productIndex;
            if (product.getProductType().equals(Consts.ProductType.BAR_A.getText())) {
                productIndex = 0;
            } else if (product.getProductType().equals(Consts.ProductType.BAR_B.getText())) {
                productIndex = 1;
            } else if (product.getProductType().equals(Consts.ProductType.BAR_C.getText())) {
                productIndex = 2;
            } else {
                productIndex = 3;
            }


//            if (il.getQtyOrderedCase() == 0) {
//                leadTimes.set(productIndex, new Timestamp(System.currentTimeMillis()));
//            } else {
            Long todayQty = 0L;
            Calendar todayCal = Calendar.getInstance();
            todayCal.set(0, 0, 0, 0, 0, 0);
            todayCal.setTime(dates.get(0).getTime());
            DailyPlannedProduction todayDpp = getDailyPlannedProductionByProductIdAndProductionDay(product.getId(), new Timestamp(todayCal.getTimeInMillis()));
            if (todayDpp != null) {
                todayQty = todayDpp.getAvailable();
            }

            Long requested = il.getQtyOrderedCase() - icSession.getRealInventory(product.getProductType()) - todayQty;
            System.out.println("ATPSession at line 661: requested quantity" + requested);

            Long available = 0l;
            for (int j = 0; j < dates.size(); j++) {  //for each date
                //calculate available amount for each date: dailydemand + ot - reservations
                //get ddp
//                Query query = entityManager.createQuery("SELECT ddp FROM DailyPlannedProduction ddp WHERE ddp.product.id= :p1 AND ddp.productionDayL = :p2");
//                query.setParameter("p1", product.getId());
//                query.setParameter("p2", dates.get(j).getTimeInMillis() / 86400000 * 86400000);
//                //TODO: for testing
//                System.out.println("product :" + product.getId() + product.getProductType());
//                System.out.println("testing :::::::::::::::::" + dates.get(j).getTimeInMillis()/86400000+"  "+new Timestamp(dates.get(j).getTimeInMillis()));
//                DailyPlannedProduction ddp = (DailyPlannedProduction) query.getSingleResult();
                DailyPlannedProduction ddp = getDailyPlannedProductionByProductIdAndProductionDay(product.getId(), new Timestamp(dates.get(j).getTimeInMillis()));
                Long reservedAmount;
                if (ddp == null) {
                    reservedAmount = 0l;
                    System.out.println("testing ::::::::: dpp null");
                } else {
                    reservedAmount = ddp.getReserveredAmount();
                    System.out.println("testing ::::::::" + reservedAmount);
                }
                available += dailyDemands.get(productIndex).get(j).getL() + otDailyDemands.get(productIndex).get(j).getL() - reservedAmount;
                System.out.println("ATPSession at line 684: available " + available);
                //check whether enough 
                if (requested <= available) {
                    //leadTimes.get(productIndex).setTime(dates.get(j).getTimeInMillis());
                    leadTimes.set(productIndex, new Timestamp(dates.get(j).getTimeInMillis()));
                    break;
                }
            }

            if (requested > available) {
                return null;
            }

//            }
        }

        Timestamp max = null;
        for (Timestamp ts : leadTimes) {
            if (max == null && ts != null) {
                max = ts;
            } else if (ts != null && max != null && max.before(ts)) {
                max = ts;
            }
        }

        return max;

    }

    private DailyPlannedProduction getDailyPlannedProductionByProductIdAndProductionDay(Long pId, Timestamp pday) {
        Query query = entityManager.createQuery("SELECT dpp FROM DailyPlannedProduction dpp WHERE dpp.product.id =:p1");
        query.setParameter("p1", pId);
        for (DailyPlannedProduction dpp : new ArrayList<DailyPlannedProduction>(query.getResultList())) {
            if (Consts.sdfDmy.format(dpp.getProductionDay()).equals(Consts.sdfDmy.format(pday))) {
                return dpp;
            }
        }
        return null;
    }

    public DailyMPS getDailyMPSbyDate(Calendar date) {
        int year, month, weekNo;
        Calendar monday = mpsSession.getFirstMondayOfMonth(date.get(Calendar.YEAR), date.get(Calendar.MONTH) + 1);

        if (date.get(Calendar.WEEK_OF_YEAR) >= monday.get(Calendar.WEEK_OF_YEAR)) {
            year = date.get(Calendar.YEAR);
            month = date.get(Calendar.MONTH) + 1;
            weekNo = date.get(Calendar.WEEK_OF_YEAR) - monday.get(Calendar.WEEK_OF_YEAR) + 1;
        } else {
            if (date.get(Calendar.MONTH) != Calendar.JANUARY) {
                year = date.get(Calendar.YEAR);
                month = date.get(Calendar.MONTH); //month + 1 - 1  previous month
            } else {
                year = date.get(Calendar.YEAR) - 1;
                month = 12;
            }
            monday = mpsSession.getFirstMondayOfMonth(year, month);
            weekNo = date.get(Calendar.WEEK_OF_YEAR) - monday.get(Calendar.WEEK_OF_YEAR) + 1;

        }
        DailyMPS dmps2 = mpsSession.getDailyMPS(year, month, weekNo);

        System.out.println("ATPSession at line 729: Year:  " + year + " Month " + month + " Day " + weekNo);
        return dmps2;
    }

    @Override
    public boolean confirmSimulationOfSalesOrder(ArrayList<ArrayList<LongHolder>> dailyDemands, ArrayList<ArrayList<LongHolder>> otDailyDemands, ArrayList<ArrayList<Long>> differences, ArrayList<Calendar> dates, PendingATP patp) {
        //simulate first
        Timestamp ts = simulateUrgentRfq(dailyDemands, otDailyDemands, differences, dates, patp);
        if (ts == null) { //cannot meet the requested lead time
            return false;
        }

        //can meet the lead time, create backorder, update MPS, create reservation.
        //update MPS
        //get all old DMPS as well (edited on 11/9 by ZX

        ArrayList<DailyMPS> dmpss = new ArrayList<DailyMPS>();
        DailyMPS dmps2 = getDailyMPSbyDate(dates.get(0));
        dmpss.add(dmps2);
        int weekNumber2 = dates.get(0).get(Calendar.WEEK_OF_YEAR);
        for (int i = 0; i < dates.size(); i++) {

            //added by ZX on 11/9 12:27pm
            if (dates.get(i).get(Calendar.WEEK_OF_YEAR) == weekNumber2) {
            } else {
                weekNumber2 = dates.get(i).get(Calendar.WEEK_OF_YEAR);  //store the previous dailyMPS
                dmps2 = getDailyMPSbyDate(dates.get(i));
                if (dmps2 != null) {
                    System.out.println("@@@@@@@@@@@@@@@@@@@ATPSession: line 756 dmps:" + dmps2.getId() + " month" + dmps2.getStartPeriodMonth() + " week:" + dmps2.getStartPeriodDate());
                } else {
                    System.out.println("@@@@@@@@@@@@@@@@@@@ATPSession: line 756 dmps  null");
                }

                DailyMPS dmpsNew = new DailyMPS(dmps2.getStartPeriodYear(), dmps2.getStartPeriodMonth(), dmps2.getStartPeriodDate(), dmps2.getDateCreated());
                for (int k = 0; k < 4; k++) {
                    for (int m = 0; m < 5; m++) {
                        dmpsNew.returnIndivDMPS(k).returnDailyPlannedProduction(m).setDailyDemand(dmps2.returnIndivDMPS(k).returnDailyPlannedProduction(m).getDailyDemand());
                        dmpsNew.returnIndivDMPS(k).returnDailyPlannedProduction(m).setDailyOTDemand(dmps2.returnIndivDMPS(k).returnDailyPlannedProduction(m).getDailyOTDemand());
                    }
                }

                dmpss.add(dmpsNew);
            }
            for (int j = 0; j < 4; j++) {
                //get corresponding dpp
                String productType = "";
                if (j == 0) {
                    productType = "Classic Nuts";
                } else if (j == 1) {
                    productType = "Classic Fruits";
                } else if (j == 2) {
                    productType = "Premium Chocolate Nuts";
                } else {
                    productType = "Premium Redcurrant Fruits";
                }
                Product p = slcSession.getProductByProductType(productType);
                DailyPlannedProduction dpp = getDailyPlannedProductionByProductIdAndProductionDay(p.getId(), new Timestamp(dates.get(i).getTimeInMillis()));
                dpp.setDailyDemand(dailyDemands.get(j).get(i).getL());
                dpp.setDailyOTDemand(otDailyDemands.get(j).get(i).getL());
                entityManager.merge(dpp);
                entityManager.flush();
            }
        }

        //feedback to sop, weekly mpsx
        for (int i = 0; i < dmpss.size(); i++) {
            DailyMPS dmps = dmpss.get(i);
            if (dmps != null) {
                System.out.println("@@@@@@@@@@@ATP Session bean at 789: " + dmps.getId() + " " + dmps.getDailyMPSIdStr() + " month:" + dmps.getStartPeriodMonth() + " week :" + dmps.getStartPeriodDate());
                ArrayList<ArrayList<Long>> dailyDemand = new ArrayList<ArrayList<Long>>();
                ArrayList<ArrayList<Long>> otDailyDemand = new ArrayList<ArrayList<Long>>();
                for (int j = 0; j < 4; j++) {
                    ArrayList<Long> indivDailyDemand = new ArrayList<Long>();
                    ArrayList<Long> indivOtDailyDemand = new ArrayList<Long>();
                    for (int k = 0; k < 5; k++) {
                        System.out.println("product " + j + " day " + k + " quantity: " + dmps.returnIndivDMPS(j).returnDailyPlannedProduction(k).getDailyDemand());
                        indivDailyDemand.add(dmps.returnIndivDMPS(j).returnDailyPlannedProduction(k).getDailyDemand());
                        System.out.println("product " + j + " day " + k + " quantity: " + dmps.returnIndivDMPS(j).returnDailyPlannedProduction(k).getDailyOTDemand());
                        indivOtDailyDemand.add(dmps.returnIndivDMPS(j).returnDailyPlannedProduction(k).getDailyOTDemand());
                    }
                    dailyDemand.add(indivDailyDemand);
                    otDailyDemand.add(indivOtDailyDemand);
                }
                DailyMPS newDMPS = mpsSession.getDailyMPS(dmps.getStartPeriodYear(), dmps.getStartPeriodMonth(), dmps.getStartPeriodDate());
                mpsSession.updateDailyMPS2(newDMPS, dailyDemand, otDailyDemand);
            }
        }
//        //feedback to sop, weekly mpsx
//        int weekNumber = dates.get(0).get(Calendar.WEEK_OF_YEAR);
//        int weekNo = getWeekNo(dates.get(0));
//        DailyMPS dmps = mpsSession.getDailyMPS(dates.get(0).get(Calendar.YEAR), dates.get(0).get(Calendar.MONTH) + 1, weekNo);
//        if (dmps != null) {
//            mpsSession.updateDailyMPS2(dmps, null, null);
//        }
//        for (int i = 0; i < dates.size(); i++) {
//            if (dates.get(i).get(Calendar.WEEK_OF_YEAR) == weekNumber) {//same week
//            } else {
//                weekNumber = dates.get(i).get(Calendar.WEEK_OF_YEAR);
//                weekNo = getWeekNo(dates.get(i));
//                dmps = mpsSession.getDailyMPS(dates.get(i).get(Calendar.YEAR), dates.get(i).get(Calendar.MONTH) + 1, weekNo);
//                if (dmps != null) {
//                    mpsSession.updateDailyMPS2(dmps, null, null);
//                }
//            }
//        }

        //edited by ZX on 11/9 12:18pm


        //create backorder
        BackOrder bo = new BackOrder();
        bo.setDeliveryOrder(patp.getDeliveryOrder());

        for (int i = 0; i < 4; i++) {
            Long total = 0l;
            for (int j = 0; j < differences.get(0).size(); j++) {
                if (differences.get(i).get(j) > 0) {  //edited by ZX on 11/9 12:41pm
                    total += differences.get(i).get(j);
                }
            }
            bo.getQuantities().get(i).setLongValue(total);
        }

        for (int i = 0; i < dates.size(); i++) {
            IndexNDate date = new IndexNDate(i, new Timestamp(dates.get(i).getTimeInMillis()));
            bo.getDates().add(date);
        }

        for (int i = 0; i < 4; i++) {
            BackOrderIndiv indivBo = new BackOrderIndiv();
            for (int j = 0; j < differences.get(i).size(); j++) {
                System.out.println("Differences for project " + i + " at date" + j + " " + differences.get(i).get(j));
                Long increase = 0l;
                if (differences.get(i).get(j) > 0) {
                    increase = differences.get(i).get(j);
                }
                IndexNValue value = new IndexNValue(j, increase);
                indivBo.getQuantities().add(value);
            }
            bo.getIndivBo().add(indivBo);
        }

        entityManager.merge(bo);

        //reserve
        reserveForSalesOrder(patp.getDeliveryOrder(), patp.getRequestedTime());
        return true;
    }

    public DailyPlannedProduction getSingleDailyPlannedProductionByDateAndProduct(Product p, Timestamp pd) {
        //query got potential bug
        Query query = entityManager.createQuery("SELECT dpp FROM DailyPlannedProduction dpp WHERE dpp.product.productType=:p1 AND dpp.productionDayL/86400000=:p2");
        query.setParameter("p1", p.getProductType());
        query.setParameter("p2", pd.getTime() / 86400000);
        return (DailyPlannedProduction) query.getSingleResult();
    }

    @Override
    public boolean checkIsWorkingDay(Calendar day) {
        if (!sopSession.isPublicHoliday(day) && day.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY && day.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean changeDOStatusAfterConfirmation(Long soId, Timestamp confirmedLT, Long deId) {
        try {
            // TODO: change so status
            //SalesOrder so = entityManager.find(SalesOrder.class, soId);
            DeliveryOrder doe = entityManager.find(DeliveryOrder.class, deId);
            doe.setLeadTimeTS(confirmedLT);
            doe.setLeadTime(Consts.sdfTime.format(confirmedLT));
            doe.getSalesOrder().setSalesOrderStatus(Consts.ORDER_WAITING_CONFIRMATION);
            entityManager.merge(doe);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean changeSQIndicativeLeadTime(Long deoId, Timestamp indicativeLeadtime) {
        try {
            DeliveryOrder deo = entityManager.find(DeliveryOrder.class, deoId);
            deo.setIndicativeLeadTimeTS(indicativeLeadtime);
            deo.setIndicativeLeadTime(Consts.sdfTime.format(indicativeLeadtime));
            entityManager.merge(deo);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean changeDOStatusAfterATPFailedSO(Long id) {
        try {
            DeliveryOrder deo = entityManager.find(DeliveryOrder.class, id);
            deo.setLeadTime(Consts.ATP_SO_FAIL);
            deo.getSalesOrder().setSalesOrderStatus(Consts.ORDER_ATPFAILED);
            entityManager.merge(deo);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean changeDOStatusAfterATPFailedRFQ(Long id) {
        try {
            DeliveryOrder deo = entityManager.find(DeliveryOrder.class, id);
            deo.setIndicativeLeadTime(Consts.ATP_SO_FAIL);
            entityManager.merge(deo);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }

    @Override
    public ArrayList<BackOrder> getAllBackOrders() {
        Query query = entityManager.createQuery("SELECT bo FROM BackOrder bo");
        return new ArrayList<BackOrder>(query.getResultList());
    }

    @Override
    public void removePendingATP(PendingATP pendingATP) {
        PendingATP p = entityManager.find(PendingATP.class, pendingATP.getId());
        p.setDeliveryOrder(null);
        entityManager.merge(p);
        entityManager.flush();
        entityManager.remove(p);
        entityManager.flush();
    }
}
